第09章 AdaBoost与GBDT模型¶

  • 集成学习模型算法

    • Bagging算法

      • 随机森林模型
    • Boosting算法

      • AdaBoost模型

      • GBDT模型

      • XGBoost模型

      • LightGBM模型

9.1 AdaBoost算法原理¶

9.1.1 AdaBoost算法的核心思想¶

AdaBoost

  • AdaBoost (Adaptive Boosting) 算法是一种有效使用的Boosting算法, 以一种高度自适应的方式按顺序训练学习器

  • 针对第分类问题, 根据前一次的分类效果调整数据的权重, 在上一个弱学习器中分类错误的样本的权重会在下一个弱学习器中增加, 分类正确的样本的权重则相应减少, 并且在每一轮迭代时会向模型加入一个新的弱学习器

  • 不断重复调整权重和训练弱学习器, 知道误分类数低于预设值或迭代次数达到指定最大值, 最终得到一个强学习器

  • 核心思想就是调整错误样本的权重, 进而迭代升级

9.1.2 AdaBoost算法的数学原理概述¶

image.png

输入训练集数据, $T = \{(x_1,y_1), (x_2,y_2), \cdots, (x_N, y_N)\}$, $x$为特征变量, $y$ 为目标变量, 其中 $x_i \in R^n, y_i \in \{-1,+1\}$

初始化各样本的权重

$$w_{1i} = \frac{1}{N} \hspace{1em} (i = 1,2,...,N)$$

计算误差率

计算弱学习器$F_m(x)$的误差率$e_m$

$$e_m = \sum_{i = 1}^{N} w_{mi} I(F_m(x_i) \neq y_i)$$

其中$w_{mi}$ 是样本$i$的权重; $F_m(x_i)$是弱学习器$F_m(x)$所预测的样本$i$的分类, 即预测值; $y_i$是样本$i$的实际值; $I(F_m(x_i) \neq y_i)$ 是一个指示函数 (Indicator Function) , 当括号内的条件成立 (即预测失败) 时, 函数取值为1, 否则为0

调整弱学习器的权重

弱学习器$F_m(x)$的系数 $ \alpha_m $ 的计算公式为 $$ \alpha_m = \frac{1}{2} ln\frac{1 - e_m}{e_m} $$

得到第 $m$ 次迭代的强学习器, 公式为 $$ f_m(x) = f_{m-1}(x) + \alpha_m F_m(x)$$ 或 $$f_m(x) = \sum_{i = 1}^m \alpha_i F_i(x)$$ 其中 $f_{m-1}(x)$ 是第$m-1$次迭代的强学习器

更新样本的权重

有了弱学习器的权重后, 就可以更新原来样本的权重: 增大分类错误的样本的权重, 减小分类正确的样本的权重, 从而在之后实现更为准确的分类

样本权重的更新公式为 $$w_{m+1, i} = \frac{w_{mi}exp(-\alpha_m y_i F_m(x_i))}{Z_m} \hspace{1em} (i = 1,2,...,N)$$ $$\sum_{i = 1}^{N} w_{m+1, i} = 1$$ 其中$w_{m+1, i}$ 是本次更新的样本权重, $w_{mi}$ 是上次的样本权重, 各样本权重之和为1; $\alpha_m$是第$m$次迭代中弱学习器$F_m(x)$的系数, $y_i$是样本$i$的实际值, $F_m(x_i)$ 是弱学习器 $F_m(x)$ 所预测的样本$i$ 的分类, $Z_m$是一个规范化因子, 其公式为 $$Z_m = \sum_{i=1}^{N} w_{mi} exp(-\alpha_m y_i F_m(x_i))$$

反复迭代

将上述过程反复迭代, 直到误分类数达到阈值或迭代次数达到设定的最大值, $M$ 次迭代后得到的最终强学习器为

$$ sign[f(x)] = sign[\sum_{i=1}^M \alpha_i F_i(x)] $$

其中$sign(x)$是符号函数, 即 $$ sign(x)= \begin{cases} 1,&x>0\\ 0, &x=0\\ -1, &x<0 \end{cases} $$

补充知识点: 正则化项¶

为防止 Adaboost算法过拟合, 可以向模型中加入正则化项(每个弱学习器的权重缩减系数$v$), 又称为学习率(learning rate)

弱学习器的迭代公式为:

$$ f_m(x) = f_{m-1}(x) + \alpha_mF_m(x) $$

加入权重所见系数后公式变为:

$$ f_m(x) = f_{m-1}(x) + v\alpha_mF_m(x) $$

$v \in (0,1]$, 取值较小意味着要达到一定的误分类数或学习效果, 需要迭代的次数更多, 需要学习的弱学习器更多

9.1.3 AdaBoost算法的数学原理距离¶

样本数据

x 1 2 3 4 5
y 1 1 -1 1 -1

第1次迭代¶

  1. 初始化各样本的权重

    $$ w_{1i} = \frac{1}{N} = 0.2 $$

    第1次迭代需要构建初始决策树, 即需要判断在$x = 1.5、x = 2.5、x = 3.5、x = 4.5$ 这4个位置中的哪个位置 "切一刀", 并判断切完后是将左边划分为1, 还是将右边划分为1, 才能使整体的误差率最小

  2. 根据误差率 $e_m$ 选择划分方法

    误差率$e_m$为分类错误的样本的权重之和:

    $$ e_m = \sum_{i = 1}^{5} w_{mi}I(F_m(x_i) \neq y_i) $$

    切法1: 切完后将左边数据划分为1的类别, 将右边数据划分为-1的类别, 先在 $x=1.5$ 处 "切一刀" 划分类别, 即设置阈值 $x = 1.5$, 意味着弱学习器 $F_m(x)$为:

    $$ F_m(x)= \begin{cases} 1, &x<1.5\\ -1, &x>1.5 \end{cases} $$

    此时预测结果为

    |x|1|2|3|4|5| |-|-|-|-|-|-| |y|1|1|-1|1|-1| |预测值|1|-1|-1|-1|-1|

    其中各样本的权重为0.2, 有两个样本预测错误, 误差率为 $e_m = 0.2 \times 0 \times 3 + 0.2 \times 1 \times 2 = 0.4$

    同样的方法, 可以计算出切法以下4种阈值的误差率

    |$v = 1.5$|$v = 2.5$|$v = 3.5$|$v = 4.5$| |-|-|-|-| |0.4|0.2|0.4|0.2|

    切法2: 切完后将左边数据划分为-1的类别, 将右边数据划分为1的类别, 同样可以计算出切法2下四种阈值的误差率

    |$v = 1.5$|$v = 2.5$|$v = 3.5$|$v = 4.5$| |-|-|-|-| |0.6|0.8|0.6|0.8|

    可以看到, 采用切法1且阈值取2.5或4.5时, 误差率 $e_1$ 最低, 为0.2

  3. 构造弱学习器

    从2.5和4.5中随机挑选一个值作为阈值, 此处选择 $v=2.5$, 所以弱学习器为

    $$ F_1(x) = \begin{cases} 1, x<2.5 \\ -1, x>2.5 \end{cases} $$

    其实这就是一个深度为1的分类决策树

  4. 计算弱学习器的系数

    利用已知的误差率 $e_1$, 计算弱学习器 $F_1(x)$ 的系数 $\alpha_1$:

    $$ \alpha_1 = \frac{1}{2} \ln \frac{1 - e_1}{e_1} = \frac{1}{2} \ln \frac{1-0.2}{0.2} = 0.6931 $$

    得到第1次迭代后的强学习器为

    $$ f_1{x} = \alpha_1F_1(x) = 0.6931F_1(x) = \begin{cases} 0.6931, x<2.5\\ -0.6931, x>2.5 \end{cases} $$

  5. 构造第1次迭代后的最终强学习器

    $$ sign[f_1(x)] = sign (\begin{cases} 0.6931, x<2.5\\ -0.6931, x>2.5 \end{cases}) = \begin{cases} 1,x<2.5\\ -1, x>2.5 \end{cases} $$

    此时的预测结果为

    |x|1|2|3|4|5| |-|-|-|-|-|-| |y|1|1|-1|1|-1| |$sign[f_1(x)]$|1|1|-1|-1|-1|

第2次迭代¶

因为第1次迭代没有达到误分类数为0的目标, 所以还需要进行第2次迭代

  1. 更新各样本的权重

    通过公式调整原始数据中各样本的权重

    $$ w_{m+1,i} = \frac{w_{mi}}{Z_m} exp(-\alpha_m y_i F_m(x_i)) \ (i=1,2,3,4,5) $$

    $Z_m$的计算公式为

    $$ Z_m = \sum_{i=1}^5 w_{mi}exp(-\alpha_m y_i F_m(x_i)) $$

    计算过程中可以将 $\alpha_m = \frac{1}{2}\ln\frac{1 - e_m}{e_m}$ 代入 $exp(-\alpha_m y_i F_m(x_i))$ 项, 即

    $$ \begin{aligned} exp(-\alpha_m y_i F_m(x_i)) &= exp(-\frac{1}{2}\ln\frac{1 - e_m}{e_m} y_i F_m(x_i)) \\ &= \sqrt{\frac{e_m}{1-e_m}}^{y_i F_m(x_i)} \end{aligned} $$

    以方便计算

    最终得到新的样本权重为

    |$X$|1|2|3|4|5| |-|-|-|-|-|-| |$w_{2i}$|$\frac{1}{8}$|$\frac{1}{8}$|$\frac{1}{8}$|$\frac{1}{2}$|$\frac{1}{8}$|

    可以发现, 在第1个弱学习器中分类错误的样本权重上升了

  2. 根据误差率 $e_m$ 选择划分方法

    切法1: 切完后将左边数据划分为1的类别, 将右边数据划分为-1的类别, 计算出以下4种阈值的误差率:

    |$v = 1.5$|$v = 2.5$|$v = 3.5$|$v = 4.5$| |-|-|-|-| | $\frac{5}{8}$ |$\frac{1}{2}$|$\frac{5}{8}$|$\frac{1}{8}$|

    切法2: 切完后将左边数据划分为-1的类别, 将右边数据划分为1的类别, 同样可以计算出切法2下四种阈值的误差率

    |$v = 1.5$|$v = 2.5$|$v = 3.5$|$v = 4.5$| |-|-|-|-| | $\frac{3}{8}$ |$\frac{1}{2}$|$\frac{3}{8}$|$\frac{7}{8}$|

    可以看到, 采用切法1且阈值取4.5时, 误差率 $e_2$ 最低, 为 $\frac{1}{8}$

  3. 构造弱学习器

    选择4.5作为阈值时的弱学习器为:

    $$ F_2(x) = \begin{cases} 1, x<4.5\\ -1, x>4.5 \end{cases} $$

  4. 计算学习器的系数

    利用已知的误差率 $e_2$ 计算系数:

    $$ \alpha_2 = \frac{1}{2}\ln \frac{1-e_2}{e_2} = 0.9730 $$

    得到第2次迭代后的强学习器为:

    $$ f_2(x) = \sum_{i=1}^2 \alpha_i F_i(x) = 0.6931F_1(x) + 0.9730F_2(x)\\ = \begin{cases} 0.6931, x<2.5\\ -0.6931, x>2.5 \end{cases} + \begin{cases} 0.9730, x<4.5\\ -0.9730, x>4.5 \end{cases} = \begin{cases} 1.6661, x<2.5\\ 0.2799, 2.5<x<4.5\\ -1.6661, x>4.5 \end{cases} $$

  5. 构造第2次迭代后的最终强学习器

    $$ sign[f_2(x)] = sign( \begin{cases} 1.6661, x<2.5\\ 0.2799, 2.5<x<4.5\\ -1.6661, x>4.5 \end{cases} ) = \begin{cases} 1, x<2.5\\ 1, 2.5<x<4.5\\ -1, x>4.5 \end{cases} $$

此时的预测结果为

x 1 2 3 4 5
y 1 1 -1 1 -1
$sign[f_2(x)]$ 1 1 1 1 -1

第3次迭代¶

因为第2次迭代没有达到误分类数为0的目标, 所以还需要进行第3次迭代

  1. 更新各样本的权重

    与第2次迭代一样, 利用公式计算此时各个样本的权重

    |$X$|1|2|3|4|5| |-|-|-|-|-|-| |$w_{3i}$|0.0714|0.0714|0.5001|0.2858|0.0714|

    对比第2次迭代中的权重, 发现在第2个学习器中分类错误的第3个样本权重上升了

  2. 根据误差率 $e_m$ 选择划分方法

    切法1: 切完后将左边数据划分为1的类别, 将右边数据划分为-1的类别, 计算出以下4种阈值的误差率:

    |$v = 1.5$|$v = 2.5$|$v = 3.5$|$v = 4.5$| |-|-|-|-| |0.3572|0.2858|0.7859|0.5001|

    切法2: 切完后将左边数据划分为-1的类别, 将右边数据划分为1的类别, 同样可以计算出切法2下四种阈值的误差率

    |$v = 1.5$|$v = 2.5$|$v = 3.5$|$v = 4.5$| |-|-|-|-| |0.6429|0.7143|0.2142|0.5000|

    可以看到, 采用切法2且阈值取3.5时, 误差率 $e_3$ 最低, 为 0.2142

  3. 构造弱学习器

    选择4.5作为阈值时的弱学习器为:

    $$ F_2(x) = \begin{cases} -1, x<3.5\\ 1, x>3.5 \end{cases} $$

  4. 计算学习器的系数

    利用已知的误差率 $e_3$ 计算系数:

    $$ \alpha_2 = \frac{1}{2}\ln \frac{1-e_3}{e_3} = 0.6499 $$

    得到第2次迭代后的强学习器为:

    $$ \begin{aligned} f_3(x) &= \sum_{i=1}^2 \alpha_i F_i(x) = 0.6931F_1(x) + 0.9730F_2(x)\\ &= \begin{cases} 0.6931, x<2.5\\ -0.6931, x>2.5 \end{cases} + \begin{cases} 0.9730, x<4.5\\ -0.9730, x>4.5 \end{cases} + \begin{cases} -0.6499, x<3.5\\ 0.6499, x>3.5 \end{cases}\\ &= \begin{cases} 1.0162, x<2.5\\ -0.3700, 2.5<x<3.5\\ 0.9298, 3.5<x<4.5\\ -1.0162, x>4.5 \end{cases} \end{aligned} $$

    可以看到, $f_3(x)$就是3棵最大深度为1的决策树集成而来的结果

  5. 构造第2次迭代后的最终强学习器

    $$ sign[f_3(x)] = sign( \begin{cases} 1.0162, &x<2.5\\ -0.3700, &2.5<x<3.5\\ 0.9298, &3.5<x<4.5\\ -1.0162, &x>4.5 \end{cases} ) = \begin{cases} 1, &x<2.5\\ -1, &2.5<x<3.5\\ 1, &3.5<x<4.5\\ -1, &x>4.5 \end{cases} $$

此时的预测结果为

x 1 2 3 4 5
y 1 1 -1 1 -1
$sign[f_3(x)]$ 1 1 -1 1 -1

9.1.4 AdaBoost算法的简单代码实现¶

In [1]:
import warnings

warnings.filterwarnings('ignore')

AdaBoost分类模型

In [2]:
from sklearn.ensemble import AdaBoostClassifier
X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
y = [0, 0, 0, 1, 1]

model = AdaBoostClassifier(random_state=123)
model.fit(X, y)

print(model.predict([[5, 5]]))
[0]

AdaBoost回归模型

In [3]:
from sklearn.ensemble import AdaBoostRegressor
X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
y = [1, 2, 3, 4, 5]

model = AdaBoostRegressor(random_state=123)
model.fit(X, y)

print(model.predict([[5, 5]]))
[3.]

9.2 AdaBoost算法案例实战: 信用卡精准营销模型¶

In [4]:
import pandas as pd
df = pd.read_excel('信用卡精准营销模型.xlsx')
df.head()
Out[4]:
年龄 月收入(元) 月消费(元) 性别 月消费/月收入 响应
0 30 7275 6062 0 0.833265 1
1 25 17739 13648 0 0.769378 1
2 29 25736 14311 0 0.556069 1
3 23 14162 7596 0 0.536365 1
4 27 15563 12849 0 0.825612 1
In [5]:
X = df.drop(columns='响应') 
y = df['响应']
In [6]:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)
In [7]:
from sklearn.ensemble import AdaBoostClassifier
clf = AdaBoostClassifier(random_state=123)
clf.fit(X_train, y_train)
Out[7]:
AdaBoostClassifier(random_state=123)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
AdaBoostClassifier(random_state=123)
In [8]:
# 模型搭建完毕后,通过如下代码预测测试集数据:
y_pred = clf.predict(X_test)
print(y_pred)
[1 1 1 0 1 0 1 0 0 0 1 1 1 1 1 0 0 1 1 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0 1 0
 1 1 0 0 0 1 1 0 0 1 0 0 0 1 0 0 0 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 1
 0 0 0 0 1 0 0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 1 1
 0 1 0 1 0 0 0 1 0 0 0 1 0 0 1 0 1 1 1 0 0 0 0 0 0 0 1 0 0 0 0 1 1 1 0 0 1
 0 1 0 1 0 0 0 0 0 1 1 0 1 0 1 1 1 0 0 1 1 0 0 0 0 1 0 1 0 0 0 1 0 1 0 1 1
 0 0 1 0 0 0 0 0 0 0 1 1 0 1 1]
In [9]:
# 可以将预测值和实际值进行对比:
a = pd.DataFrame()  # 创建一个空DataFrame 
a['预测值'] = list(y_pred)
a['实际值'] = list(y_test)
a.head()
Out[9]:
预测值 实际值
0 1 1
1 1 1
2 1 1
3 0 0
4 1 1
In [10]:
# 查看预测准确度
from sklearn.metrics import accuracy_score
score = accuracy_score(y_pred, y_test)
print(score)
0.85
In [11]:
# 查看预测分类概率
y_pred_proba = clf.predict_proba(X_test)
y_pred_proba[0:5]  # 查看前5项,第一列为分类为0的概率,第二列为分类为1的概率
Out[11]:
array([[0.19294615, 0.80705385],
       [0.41359387, 0.58640613],
       [0.42597039, 0.57402961],
       [0.6681739 , 0.3318261 ],
       [0.32850159, 0.67149841]])
In [12]:
# 绘制ROC曲线
from sklearn.metrics import roc_curve
fpr, tpr, thres = roc_curve(y_test.values, y_pred_proba[:,1])
import matplotlib.pyplot as plt
plt.plot(fpr, tpr)
plt.show()
No description has been provided for this image
In [13]:
# 查看AUC值
from sklearn.metrics import roc_auc_score
score = roc_auc_score(y_test, y_pred_proba[:,1])
print(score)
0.9559047909673483
In [14]:
# 查看特征重要性
clf.feature_importances_
Out[14]:
array([0.18, 0.2 , 0.36, 0.02, 0.24])
In [15]:
# 通过DataFrame的方式展示特征重要性
features = X.columns  # 获取特征名称
importances = clf.feature_importances_  # 获取特征重要性

# 通过二维表格形式显示
importances_df = pd.DataFrame()
importances_df['特征名称'] = features
importances_df['特征重要性'] = importances
importances_df.sort_values('特征重要性', ascending=False)
Out[15]:
特征名称 特征重要性
2 月消费(元) 0.36
4 月消费/月收入 0.24
1 月收入(元) 0.20
0 年龄 0.18
3 性别 0.02
In [16]:
# # 分类模型,通过如下代码可以查看官方介绍
# from sklearn.ensemble import AdaBoostClassifier
# AdaBoostClassifier?
In [17]:
# # 回归模型,通过如下代码可以查看官方介绍
# from sklearn.ensemble import AdaBoostRegressor
# AdaBoostRegressor?

9.3 GBDT算法原理¶

9.3.1 GBDT算法的核心思想¶

GBDT: Gradient Boosting Decision Tree(梯度提升树)

与AdaBoost算法的区别在于: AdaBoost算法根据分类效果调整权重并不断迭代, 最终生成强学习器; GBDT算法则将损失函数的负梯度作为残差的近似值, 不断使用残差迭代和拟合回归树, 最终生成强学习器. 简单来说, AdaBoost算法是调整权重, 而GBDT算法则是拟合残差

样本数据

客户 $X_1$ $X_2$ $y$
A 24 10000 8000
B 28 20000 30000
C 32 15000 25000
D 30 25000 40000

特征变量 $X_1$ 为年龄, $X_2$ 为月收入(元), 目标变量 $y$ 是实际信用卡额度(元)

假设建立的第1棵决策树为

决策树

A、C被划分到左节点, A的实际信用卡额度为8000, 而预测值为10000, 因此A的残差为8000-10000=-2000, 同理C的残差为5000, B的残差为-5000, D的残差为5000

GBDT算法的核心思想: 构造第2棵决策树来拟合第1棵决策树产生的残差, 注意这里拟合的是残差, 构造的拟合残差的决策树为:

image.png

在这棵树中, A的实际残差为-2000, 而预测残差为-3000, 那么此时A的新残差, 即残差的残差为-2000-(-3000)=1000, 同理, B的新残差为-5000-(-5000) =0, C的新残差为5000-5000=0, D的新残差为5000-5000=0

继续使用第2棵树的参擦去拟合第3棵树, 并不断重复此步骤, 使残差变小

因此, 最终的模型就是集成在一起的多个模型

image.png

9.3.2 GBDT算法的数学原理概述¶

迭代模型为

$$ f_m(x) = f_{m-1}(x) + T_m(x) $$

其中 $T_m(x)$ 是本次待搭建的决策树, 其实就是拟合上一个模型残差值得决策树, $f_m(x)$ 是本次迭代后产生的新模型

算法步骤为:

  1. 初始化 $f_0(x) = 0$

  2. 当 $m = 1,2,\cdots,M$, 计算残差 $r_{mi} = y_i - f_{m-1}(x)$; 拟合残差, 得到决策树 $T_m(x)$; 更新 $f_{m}(x) = f_{m-1}(x) + T_m(x)$

  3. 当误差或迭代次数达到指定要求时, 得到回归问题提升树

    $$ f_M(x) = \sum_{m=1}^M T_m(x) $$

9.3.3 GBDT算法的数学原理举例¶

样本数据

x 1 2 3 4 5
y 0 0 2 2 4

构造第1个模型¶

首先初始化 $f_0(x) = 0$, 然后构造第一个回归决策树模型 $f_1(x)$, 也就是 $T_1(x)$

回归决策树与分类决策树模型最大的不同就是其划分标准不是信息熵或基尼系数, 而是均方误差 $MSE$, 计算公式为

$$ MSE = \frac{1}{n} \sum(y_i - f(x_i))^2 $$

其中 $n$ 为样本数, $y_i$ 为实际值, $f(x_i)$ 为拟合值

  1. 寻找合适的初始切分点

    若设置阈值为 $v = 1.5$, 则弱学习器 $f_1(x)$为

    $$ f_1(x)= \begin{cases} 0, x<1.5 \\ 2, x>1.5 \end{cases} $$

    回归决策树中某个节点的预测值是该节点中所有数据的均值

    因此对于弱学习器 $f_1(x)$ ,其所有满足 $x>1.5$ 的值都被预测为 $\frac{0+2+2+4}{4} = 2$

    此时的残差为

    |$x$|1|2|3|4|5| |-|-|-|-|-|-| |$y$|0|0|2|2|4| |$f(x)$|0|2|2|2|2| |残差|0|-2|0|0|2|

    此时的均方误差 $MSE$ 为:

    $$ MSE = \frac{1}{5} ((0-0)^2 + (0-2)^2 + (2-2)^2 + (2-2)^2 + (4-2)^2) = 1.6 $$

    同样地, 可以计算出其他3个阈值下的均方误差 $MSE$:

    |$v=1.5$|$v=2.5$|$v=3.5$|$v=4.5$| |-|-|-|-| |1.6|0.53|0.93|0.8|

    当 $v = 2.5$ 时均方误差 $MSE$ 取得最小值, 此时第1棵决策树 $f_1(x)$ 为:

    image.png

    残差为:

    image-2.png

  1. 拟合残差获得第2个模型

    接下来就需要根据第1个模型的残差来拟合出决策树 $T_2(x)$

    步骤与第1个模型的拟合基本一致, 得到的第2棵决策树 $T_2(x)$ 为:

    image.png

    此时(残差)的残差为:

    image-2.png

    此时的集成模型 $f_2(x)$ 为:

    $$ f_2(x) = f_1(x) + T_2(x) = \begin{cases} 0, x<2.5\\ \frac{8}{3}, x>2.5 \end{cases} + \begin{cases} -\frac{1}{3}, x<4.5\\ \frac{4}{3}, x>4.5 \end{cases} = \begin{cases} -\frac{1}{3}, x<2.5\\ \frac{7}{3}, 2.5<x<4.5\\ 4, x>4.5 \end{cases} $$

    有了集成模型 $f_2(x)$ 后就可以求得此时系统的残差 $y - f(x)$:

    image-3.png

    GBDT算法时不停地拟合新模型的残差, 所以随着新的迭代, 整个系统的残差会越来越小, 系统的均方误差 $MSE$ 会越来越小, 从而使模型更加准确

补充知识点: 梯度提升树中梯度的理解¶

  • 之前定义的残差为 $y_i - f(x_i)$, 而实际应用中, GBDT(Gradient Boosting Decision Tree) 梯度提升树使用损失函数的负梯度在当前的模型的值作为残差的近似值

    • 负梯度的定义为

      $$ - \frac{\partial L (y_i,f(x_i))}{\partial f(x_i)} $$

      其中 $\partial L (y_i,f(x_i))$ 为损失函数

      • 这个负梯度在特定损失函数的情况下, 就是之前定义的残差 $y_i - f(x_i)$:

        令损失函数为

        $$ L (y_i,f(x_i)) = \frac{1}{2}(y_i - f(x_i))^2 $$

        此时对损失函数求负梯度:

        $$ - \frac{\partial \frac{1}{2}(y_i - f(x_i))^2}{\partial f(x_i)} = y_i - f(x_i) $$

        也就是说, 当损失函数是平方函数时, 负梯度就是残差, 当损失函数不是平方函数时, 负梯度只是残差的近似值, 并不完全等于残差

9.3.4 GBDT算法的简单代码实现¶

  1. GBDT分类模型的简单演示代码
In [18]:
from sklearn.ensemble import GradientBoostingClassifier
X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
y = [0, 0, 0, 1, 1]

model = GradientBoostingClassifier(random_state=123)
model.fit(X, y)

print(model.predict([[5, 5]]))
[0]
  1. GBDT回归模型的简单代码演示
In [19]:
from sklearn.ensemble import GradientBoostingRegressor
X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
y = [1, 2, 3, 4, 5]

model = GradientBoostingRegressor(random_state=123)
model.fit(X, y)

print(model.predict([[5, 5]]))
[2.54908866]

9.4 GBDT算法案例实战: 产品定价模型¶

9.4.1 案例背景¶

出版社在对图书进行定价时会考虑图书的页数、纸张、类别、内容、作者及读者等很多因素,用人工来分析较为烦琐,并且容易遗漏

如果能建立一个模型综合考虑各方面因素对图书进行定价,那么就能更加科学合理地节约成本、提升效率,并在满足读者需求的同时促进销售,挖掘更多潜在利润

该产品定价模型也可以用于其他领域的产品定价,如金融产品的定价

9.4.2 模型搭建¶

In [20]:
import pandas as pd
df = pd.read_excel('产品定价模型.xlsx')

df.head()
Out[20]:
页数 类别 彩印 纸张 价格
0 207 技术类 0 双胶纸 60
1 210 技术类 0 双胶纸 62
2 206 技术类 0 双胶纸 62
3 218 技术类 0 双胶纸 64
4 209 技术类 0 双胶纸 60
In [21]:
print(df['类别'].value_counts(), '\n')

print(df['彩印'].value_counts(), '\n')

print(df['纸张'].value_counts(), '\n')
类别
技术类    336
教辅类    333
办公类    331
Name: count, dtype: int64 

彩印
0    648
1    352
Name: count, dtype: int64 

纸张
双胶纸    615
铜版纸    196
书写纸    189
Name: count, dtype: int64 

In [22]:
# 使用LabelEncoder()函数将文本内容转为数值

from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df['类别'] = le.fit_transform(df['类别'])  # 处理类别
In [23]:
# 将类别一列处理后,我们可以使用value_counts()方法查看转化效果:
print(df['类别'].value_counts())
类别
1    336
2    333
0    331
Name: count, dtype: int64
In [24]:
# 另外一种文本内容转为数值的方法,注意不要再运行完上面的代码后运行,因为上面的内容已经被替代完毕了,如果想尝试,需要重新运行,并且,先运行下面的代码
# df['类别'] = df['类别'].replace({'办公类': 0, '技术类': 1, '教辅类': 2})  
# df['类别'].value_counts()
In [25]:
# 下面我们使用同样的方法处理“纸张”一列:
le = LabelEncoder()
df['纸张'] = le.fit_transform(df['纸张'])
In [26]:
# 此时的表格如下:
df.head()
Out[26]:
页数 类别 彩印 纸张 价格
0 207 1 0 1 60
1 210 1 0 1 62
2 206 1 0 1 62
3 218 1 0 1 64
4 209 1 0 1 60
In [27]:
X = df.drop(columns='价格') 
y = df['价格']  
In [28]:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)
In [29]:
from sklearn.ensemble import GradientBoostingRegressor
model = GradientBoostingRegressor(random_state=123)
model.fit(X_train, y_train)
Out[29]:
GradientBoostingRegressor(random_state=123)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
GradientBoostingRegressor(random_state=123)
In [30]:
# 模型搭建完毕后,通过如下代码预测测试集数据:
y_pred = model.predict(X_test)
print(y_pred[0:50])
[ 71.15004038  79.56199921  68.21751792  90.78788507  78.88479128
  42.28022702  39.27334177  60.74670841  53.59744659  77.65931771
  80.22295545  76.04437155  79.56199921  58.40372895  79.65245266
  44.27997693  53.18177447  35.31452467  92.1798291   58.40372895
  41.96644278  99.50466356  80.22295545  79.69648341  91.45061741
  42.93885741  42.86973046  75.71824996  48.55203652  62.94185778
  39.47077874  61.54190648  95.18389309  51.88118394  65.1293139
  50.17577837  39.54495179  83.63542315  56.24632221 102.1176112
  48.89080247  49.23639342  33.03502962  52.74862135  35.47220867
  35.00370671  53.9446399   74.62364353  35.31452467  53.9446399 ]
In [31]:
# 通过和之前章节类似的代码,我们可以将预测值和实际值进行对比:
a = pd.DataFrame()  # 创建一个空DataFrame 
a['预测值'] = list(y_pred)
a['实际值'] = list(y_test)
a.head()
Out[31]:
预测值 实际值
0 71.150040 75
1 79.561999 84
2 68.217518 68
3 90.787885 90
4 78.884791 85
In [32]:
# 查看预测评分 - 方法1:自带的score函数,本质就是R-squared值(也即统计学中常说的R^2)
model.score(X_test, y_test)
Out[32]:
0.8741691363311168
In [33]:
# 查看预测评分 - 方法2:r2_score()函数
from sklearn.metrics import r2_score
r2 = r2_score(y_test, model.predict(X_test))
print(r2)
0.8741691363311168
In [34]:
# 通过DataFrame的方式展示特征重要性
features = X.columns  # 获取特征名称
importances = model.feature_importances_  # 获取特征重要性

# 通过二维表格形式显示
importances_df = pd.DataFrame()
importances_df['特征名称'] = features
importances_df['特征重要性'] = importances
importances_df.sort_values('特征重要性', ascending=False)
Out[34]:
特征名称 特征重要性
0 页数 0.490702
1 类别 0.447187
2 彩印 0.041615
3 纸张 0.020496

9.4.4 模型参数介绍¶

In [35]:
# # 分类模型,通过如下代码可以查看官方介绍
# from sklearn.ensemble import GradientBoostingRegressor
# GradientBoostingRegressor?

image.png

image-2.png

image-3.png

In [36]:
# # 回归模型,通过如下代码可以查看官方介绍
# from sklearn.ensemble import GradientBoostingClassifier
# GradientBoostingClassifier?

GBDT分类模型的常用参数与GBDT回归模型基本一致, 唯一的不同是多了一个loss参数

image.png

补充知识点: 损失函数¶

  • 损失函数是用来衡量拟合程度, 判断模型拟合效果

    实质是根据实际值和预测值间的距离评估模型的好坏, 常用的损失函数 $L(y, f(x))$ 有:

    image.png

  • 风险函数可以认为是平均意义下的损失, 又称为期望损失:

    $$ \frac{1}{N} \sum_{i=1}^N L(y_i, f(x_i)) $$

  • 损失函数并非越小越好, 若损失函数过小, 容易出现过拟合的问题, 为了解决这个问题, 可以在损失函数中加入正则化项或惩罚项:

    $$ \frac{1}{N} \sum_{i=1}^N L(y_i, f(x_i)) + \lambda J(f) $$

    其中 $\lambda$ 是正则化系数, $J(f)$ 是正则化项